using UnityEngine;

// 该类实现了一个基于 GPU 计算的高性能数学函数图形绘制器
// 它使用 ComputeShader 在 GPU 上并行计算每个点的位置，然后通过 Material 和 Mesh 实例化绘制出来
public class GPUGraph : MonoBehaviour
{

	// 定义支持的最大分辨率，也就是网格点的最大数量（避免分配过大显存）
	const int maxResolution = 1000;

	// 下面是一组 Shader Property ID，用于在 C# 和 Shader 之间高效传递参数
	// 这些 ID 代表 Shader 中的属性名，比如 "_Positions"、"_Resolution" 等
	// 使用 PropertyToID 可以提高运行时性能，避免每次 GetStringHash
	static readonly int
		positionsId = Shader.PropertyToID("_Positions"),         // 用于传递所有点位置的 Buffer
		resolutionId = Shader.PropertyToID("_Resolution"),       // 传递网格分辨率（如 10x10）
		stepId = Shader.PropertyToID("_Step"),                   // 每个点之间的间隔（步长）
		timeId = Shader.PropertyToID("_Time"),                   // 当前时间，用于动态函数
		transitionProgressId = Shader.PropertyToID("_TransitionProgress"); // 过渡进度（0~1），用于函数切换动画

	// 引用一个 ComputeShader，用于在 GPU 上计算每个顶点的位置
	[SerializeField]
	ComputeShader computeShader;

	// 用于最终渲染的 Material，它会接收 GPU 计算出来的点位置并绘制出来
	[SerializeField]
	Material material;

	// 用于绘制点的 Mesh，通常是一个简单的点、四边形或 sprite
	[SerializeField]
	Mesh mesh;

	// 用户可调节的网格分辨率，控制点的数量和画面精细度，范围 10 ~ 1000
	[SerializeField, Range(10, maxResolution)]
	int resolution = 10;

	// 当前要绘制的数学函数类型，来自 FunctionLibrary
	[SerializeField]
	FunctionLibrary.FunctionName function;

	// 定义函数切换的模式：循环（Cycle）或随机（Random）
	public enum TransitionMode { Cycle, Random }

	// 当前使用的切换模式
	[SerializeField]
	TransitionMode transitionMode;

	// 每个函数显示的持续时间（秒）
	[SerializeField, Min(0f)]
	float functionDuration = 1f;

	// 函数之间过渡动画的持续时间（秒）
	[SerializeField, Min(0f)]
	float transitionDuration = 1f;

	// 当前函数已经运行的时间
	float duration;

	// 是否正在两个函数之间进行过渡
	bool transitioning;

	// 当前过渡的起点函数
	FunctionLibrary.FunctionName transitionFunction;

	// ComputeBuffer 用于在 CPU 和 GPU 之间传递大量的点位置数据（顶点缓冲区）
	ComputeBuffer positionsBuffer;

	// 当该脚本被启用时（比如场景加载或手动启用），初始化 GPU 缓冲区
	void OnEnable()
	{
		// 创建一个 ComputeBuffer，用于存储所有点的位置
		// 总点数 = resolution * resolution，每个点有 3 个 float（x, y, z），每个 float 占 4 字节
		positionsBuffer = new ComputeBuffer(maxResolution * maxResolution, 3 * 4);
	}

	// 当该脚本被禁用时（比如停止运行或销毁对象），释放 GPU 缓冲区，避免内存泄漏
	void OnDisable()
	{
		if (positionsBuffer != null)
		{
			positionsBuffer.Release(); // 释放 ComputeBuffer
			positionsBuffer = null;
		}
	}

	// 每一帧调用一次，处理函数切换逻辑、更新 GPU 计算和绘制
	void Update()
	{
		// 累加时间差
		duration += Time.deltaTime;

		// 如果当前处于函数过渡状态
		if (transitioning)
		{
			// 如果过渡时间超过设定值，结束过渡
			if (duration >= transitionDuration)
			{
				duration -= transitionDuration;
				transitioning = false;
			}
		}
		// 如果没在过渡中，但当前函数已显示超过规定时间
		else if (duration >= functionDuration)
		{
			// 减去已显示时间，保留余数
			duration -= functionDuration;
			// 开始过渡
			transitioning = true;
			// 记录当前函数，作为过渡起点
			transitionFunction = function;
			// 选择下一个函数（根据 Cycle 或 Random 模式）
			PickNextFunction();
		}

		// 调用核心方法：在 GPU 上更新函数计算
		UpdateFunctionOnGPU();
	}

	// 根据当前模式，选择下一个要显示的函数
	void PickNextFunction()
	{
		function = transitionMode == TransitionMode.Cycle ?
			FunctionLibrary.GetNextFunctionName(function) : // 循环模式：顺序下一个
			FunctionLibrary.GetRandomFunctionNameOtherThan(function); // 随机模式：随机选一个不同的
	}

	// 🎯 核心方法：在 GPU 上执行函数计算，并将结果绘制出来
	void UpdateFunctionOnGPU()
	{
		// 计算每个点之间的步长，范围是 [-1, 1]，所以步长为 2 / resolution
		float step = 2f / resolution;

		// 设置 ComputeShader 中的各种参数
		computeShader.SetInt(resolutionId, resolution);           // 传递分辨率
		computeShader.SetFloat(stepId, step);                     // 传递步长
		computeShader.SetFloat(timeId, Time.time);                // 传递当前时间，用于动态函数效果
		if (transitioning)
		{
			// 如果正在过渡，传递过渡进度（0 ~ 1，通过 SmoothStep 平滑处理）
			computeShader.SetFloat(
				transitionProgressId,
				Mathf.SmoothStep(0f, 1f, duration / transitionDuration)
			);
		}

		// 根据当前函数和是否过渡，计算要运行的 ComputeShader Kernel（计算内核）索引
		var kernelIndex =
			(int)function +
			(int)(transitioning ? transitionFunction : function) *
			FunctionLibrary.FunctionCount;

		// 将用于存储所有点位置的 Buffer 传递给 ComputeShader
		computeShader.SetBuffer(kernelIndex, positionsId, positionsBuffer);

		// 计算 Dispatch 的线程组数量，每个线程组处理 8x8 个点，向上取整
		int groups = Mathf.CeilToInt(resolution / 8f);
		// 启动 ComputeShader 的计算，执行指定 Kernel，分组数为 groups x groups x 1
		computeShader.Dispatch(kernelIndex, groups, groups, 1);

		// 将 GPU 计算得到的点位置 Buffer 传递给渲染 Material
		material.SetBuffer(positionsId, positionsBuffer);
		material.SetFloat(stepId, step); // 同样传递步长给 shader

		// 定义一个包围盒（Bounds），用于 Graphics.DrawMeshInstancedProcedural 正确裁剪和渲染
		var bounds = new Bounds(Vector3.zero, Vector3.one * (2f + 2f / resolution));

		// 使用 Graphics.DrawMeshInstancedProcedural 绘制所有点
		// 该方法用于高性能实例化绘制，无需逐个提交 Mesh，而是直接绘制 resolution * resolution 个实例
		Graphics.DrawMeshInstancedProcedural(
			mesh,                  // 要绘制的 Mesh（比如一个点）
			0,                     // Mesh 的 submesh 索引
			material,              // 用于渲染的 Material
			bounds,                // 包围盒，用于视锥剔除等
			resolution * resolution // 实例数量，即总的点数
		);
	}
}